#!/usr/bin/env python

"""Output a PNG describing network topology of a Dingo application.
Requires graphviz to be installed from U{http://www.graphviz.org}

Copyright (C) Sarah Mount, 2008.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA

TODO:

 * Add options parser
 * Document
 * Think about directionality...

File format:

Sensor ID,x-coord,y-coord,Radio range
Sensor[3],363,77,100
Sensor[0],180,192,100
Sensor[2],223,114,100
Sensor[4],223,405,100
Sensor[1],384,494,100
"""

import math

filename = 'topology.dot'

frontmatter = 'graph G {'
backmatter  = '}'

SCALE = 10
"""@var: Stradavarious constant to fix graphviz.
@type: number
"""

def parseTopology(filename):
    """Parse a CSV file from Dingo containing a network topology.
    Returns dict indexed on sensor name containing a tuple of:
      - Sensor x-coordinate
      - Sensor y-coordinate
      - Sensor radio radius

    @type filename: str
    @param filename: Path to topology.csv file on disk.
    @rtype: dict
    """
    thefile = file(filename)
    (id, xt, yt, rt) = ('', '', '', '')
    graph = {}
    for line in thefile:
        (id, xt, yt, rt) = tuple(line.split(','))
        try:
            # Have to scale here otherwise graphviz core dumps!
            graph[id] = (float(xt)/SCALE, float(yt)/SCALE, float(rt)/SCALE)
        except ValueError:
            continue
    return graph

def generateNodes(graph):
    """Generate nodes for graphviz in appropriate format.

    @type graph: dict
    @param graph: Network topology as returned by L{parseTopology}
    @rtype: list
    """
    nodes = []
    def getXY(id):
        return (graph[id][0], graph[id][1])
    for node in graph.keys():
        x,y = getXY(node)
        nodes.append(node + ' [ pos = "' + str(x) + ',' + str(y) + '!" ];')
    return nodes

def dist((x1, y1), (x2, y2)):
    """Return Euclidean distance between two points.
    
    @rtype: float
    """
    return math.sqrt(abs(x2-x1)**2 + abs(y2-y1)**2)

def generateArcs(graph):
    """Generate arcs for graphviz in appropriate format.

    @type graph: dict
    @param graph: Network topology as returned by L{parseTopology}
    @rtype: list
    """
    arcs = []
    arcpairs = []
    def getXY(id):
        return (graph[id][0], graph[id][1])
    def getXYUnscaled(id):
        return (graph[id][0]*SCALE, graph[id][1]*SCALE)
    for node in graph.keys():
        for node2 in graph.keys():
            if node == node2:
                continue
            distance = dist(getXY(node), getXY(node2))
            if distance < graph[node][2]:
                if (node, node2) in arcpairs:
                    continue # Avoid duplicate nodes
                arcpairs.append((node, node2))
                arcpairs.append((node2, node))
                label = dist(getXYUnscaled(node), getXYUnscaled(node2))
                arcs.append(node + ' -- ' + node2 + '[ label="' + str(label) + '" ] ;')
    return arcs            

def generateNeato(graph):
    """Generate and write to disk a dot file to be processed by Neato.

    @type graph: dict
    @param graph Network topology as returned by L{parseTopology}
    """
    nodes = generateNodes(graph)
    arcs = generateArcs(graph)
    fh = open(filename, 'w')
    fh.write(frontmatter + '\n')
    for line in nodes + arcs:
        fh.write(line + '\n')
    fh.write(backmatter)
    fh.close()
    return

if __name__ == '__main__':
    import os, sys
    infile = sys.argv[1]
    generateNeato(parseTopology(infile))
    os.system('neato -v -Tpng ' + filename + ' -otopology.png ')
